package vosk;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.vosk.LibVosk;
import org.vosk.LogLevel;
import org.vosk.Model;
import org.vosk.Recognizer;

import com.orctom.vad4j.VAD;
import com.orctom.vad4j.VadMode;
import com.orctom.vad4j.VadWindowSize;

import cn.rmshadows.joffice.JofficeApp;
import cn.rmshadows.joffice.JofficeToolKit;
import cn.rmshadows.joffice.PrimaryController;
import func_controller.VoskController;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

/**
 * @author ryan
 * @description:
 * @param:
 * @return:
 */
public class VoskUtil {
	// 当前计算机CPU核心数
	static final int nThreads = Runtime.getRuntime().availableProcessors();

	/**
	 * 将音频转为vosk需要的格式
	 * 
	 * @param file_path 源文件
	 * @return 文件路径
	 */
	public static String convert2VoskWaveFile(String file_path) {
		try {
			// 首先检查是否为WAV文件,不是就转换
			if (!file_path.substring(file_path.length() - 3).equals("wav")) {
				file_path = JofficeToolKit.convertTo16k1cAudioFile(file_path, 1).getAbsolutePath();
			}
			// 检查文件是否符合VOSK条件
			if (!JofficeToolKit.checkWavFile(file_path, 1, 16, 16000)) {
				file_path = JofficeToolKit.convertTo16k1cAudioFile(file_path, 1).getAbsolutePath();
				System.out.println("\n\n\nFFF22:"+file_path);
			}
			return file_path;
		} catch (Exception e) {
			e.printStackTrace();
			VoskController.vosk_bt_status(1, false);
		}
		return null;
	}

	/**
	 * 提取vosk识别的最终结果。
	 * 
	 * @param vosk_recognized String 识别结果
	 * @param type            int 0:result 1:partial
	 * @return String 结果
	 */
	public static String voskResult(String vosk_recognized, int type) {
		if (type == 0) {
			int text_beg = vosk_recognized.lastIndexOf("\"text\" : \"");
			int text_end = vosk_recognized.lastIndexOf("}");
			String result = vosk_recognized.substring(text_beg + 10, text_end - 2);
			return result;
		} else if (type == 1) {
			try {
				int text_beg = vosk_recognized.lastIndexOf("\"partial\" : \"");
				int text_end = vosk_recognized.lastIndexOf("}");
				String result = vosk_recognized.substring(text_beg + 13, text_end - 2);
				return result;
			} catch (Exception e) {
				// handle exception {"text": ""}
//				System.out.println("Text:");
				int text_beg = vosk_recognized.lastIndexOf("\"text\" : \"");
				int text_end = vosk_recognized.lastIndexOf("}");
				String result = vosk_recognized.substring(text_beg + 10, text_end - 2);
				return result;
			}
		} else {
			System.out.println("ERROR：错误参数。仅接受0：Result、1：Partial Result。");
			return null;
		}
	}

	/**
	 * 检查识别结果是否有中断符号 如[SPK] [FIL]
	 * 
	 * @param vosk_result Vosk识别结果
	 * @return 是否进行重新识别
	 */
	public static boolean revoskSign(String vosk_result) {
		boolean isrevosk = false;
		// 中断信号 数组
		String[] revosk_sign = new String[] { "[SPK]", "[FIL]" };
		for (String sign : revosk_sign) {
			// 开头结尾包含中断信号的
			if (vosk_result.substring(0, 5).contains(sign) || vosk_result.endsWith(sign)) {
				isrevosk = true;
			}
		}
		return isrevosk;
	}

	/**
	 * 语音文件识别测试方法。仅用作测试。
	 * 
	 * @param audio_file WAV PCM 16khz 16bit单声道 256 kbps
	 * @param model_dir  语音识别模型
	 */
	public static void voskFileTest(String audio_file, String model_dir) {
		LibVosk.setLogLevel(LogLevel.DEBUG);
		try (Model model = new Model(model_dir);
				InputStream ais = AudioSystem
						.getAudioInputStream(new BufferedInputStream(new FileInputStream(audio_file)));
				Recognizer recognizer = new Recognizer(model, 16000)) {

			int nbytes;
			byte[] b = new byte[4096];
			while ((nbytes = ais.read(b)) >= 0) {
				if (recognizer.acceptWaveForm(b, nbytes)) {
					System.err.println("【识别结果】: " + voskResult(recognizer.getResult(), 0));
//					System.out.println("F:"+voskResult(recognizer.getFinalResult(), 0));
				} else {
					System.out.println("【实时识别结果】: " + voskResult(recognizer.getPartialResult(), 1));
//					recognizer.getPartialResult();
				}
			}

			System.err.println("【识别结果】: " + voskResult(recognizer.getFinalResult(), 0));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 语音文件识别。
	 * 
	 * @param audio_file WAV PCM 16khz 16bit单声道 256 kbps
	 * @param model_dir  语音识别模型
	 */
	public static void voskVoiceFile(String audio_file, String model_dir) {
		// 设置识别状态为正在识别
		VoskController.vosk_bt_status(1, true);
		LibVosk.setLogLevel(LogLevel.DEBUG);
		try (Model model = new Model(model_dir);
				InputStream ais = AudioSystem
						.getAudioInputStream(new BufferedInputStream(new FileInputStream(audio_file)));
				Recognizer recognizer = new Recognizer(model, 16000)) {

			int nbytes;
			byte[] b = new byte[4096];
			VoskController.vosk_get.appendText("\n");
			while ((nbytes = ais.read(b)) >= 0) {
				// isvosking 停止
				if (!VoskController.is_vosking || PrimaryController.fv_index != 0) {
					// 设置识别状态为停止识别
					VoskController.vosk_bt_status(1, false);
					System.out.println("Log：Vosk_File识别被终止(isVosking & FV_Changing) " + VoskController.is_vosking
							+ "    " + PrimaryController.fv_index);
					return;
				}
				if (recognizer.acceptWaveForm(b, nbytes)) {
//					System.out.println(voskResult(recognizer.getResult(), 0));
					VoskController.vosk_get.appendText(voskResult(recognizer.getResult(), 0));
//					String vosk_re = voskResult(recognizer.getResult(), 0);
//					System.out.println("F:"+voskResult(recognizer.getFinalResult(), 0));
				} else {
//					System.out.println("P:"+voskResult(recognizer.getPartialResult(), 1));
					recognizer.getPartialResult();
				}
			}

//			System.out.println(voskResult(recognizer.getFinalResult(), 0));
			VoskController.vosk_get.appendText(voskResult(recognizer.getResult(), 0));
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
		} catch (FileNotFoundException e) {
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
			// 弹出错误指示框
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					Alert alert = new Alert(AlertType.ERROR);
					alert.setTitle("错误");
					alert.setHeaderText(null);
					alert.setContentText("文件不存在！");
					alert.showAndWait();
				}
			});
			e.printStackTrace();
		} catch (IOException e) {
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
			e.printStackTrace();
		}
	}

	/**
	 * 语音文件识别 - 多线程：一个文件一个线程。
	 * 
	 * @param task_files 文件列表+索引 WAV PCM 16khz 16bit单声道 256 kbps
	 * @param model_dir  语音识别模型
	 */
	public static void voskVoiceFiles(LinkedHashMap<File, Integer> task_files, String model_dir) {
		// 设置识别状态为正在识别
		VoskController.vosk_bt_status(1, true);
		// 最终识别结果
		ConcurrentHashMap<File, LinkedList<String>> vosk_voice_files = new ConcurrentHashMap<>();
		// 添加文件 不按顺序
		for (Map.Entry<File, Integer> entry : task_files.entrySet()) {
			vosk_voice_files.put(entry.getKey(), new LinkedList<String>());
		}
		LibVosk.setLogLevel(LogLevel.DEBUG);

		try (Model model = new Model(model_dir); Recognizer recognizer = new Recognizer(model, 16000)) {
			// 线程池的核心线程数：n个
			ExecutorService es = null;
			if (nThreads <= 1) {
				es = Executors.newFixedThreadPool((int) nThreads);
			} else {
				es = Executors.newFixedThreadPool((int) nThreads - 1);
			}

			// List集合用来存储Future
			LinkedList<Future<LinkedList<String>>> vosk_result_ll = new LinkedList<>();

			// 提交任务
			for (Map.Entry<File, LinkedList<String>> entry : vosk_voice_files.entrySet()) {
//				System.out.println("Key: " + entry.getKey() + " Value: " + entry.getValue());
				// 文件转流
				InputStream ais = AudioSystem
						.getAudioInputStream(new BufferedInputStream(new FileInputStream(entry.getKey().getPath())));
				byte[] task_byte = ais.readAllBytes();
				// 提交任务之后，返回结果是Future，此时线程还没有结束，等待任务的返回值
				Future<LinkedList<String>> s = es
						.submit(new VoskTaskThread(task_files.get(entry.getKey()), task_byte, model_dir));
				vosk_result_ll.add(s);
			}

			// 获取结果
			for (Future<LinkedList<String>> f : vosk_result_ll) {
				try {
					// 获取任务ID
					int task_id = Integer.valueOf(f.get().getFirst());
					f.get().removeFirst();
					vosk_voice_files.replace(getKeyFileList(task_files, task_id).get(0), f.get());
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				}
			}
			es.shutdown();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
			// 弹出错误指示框
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					Alert alert = new Alert(AlertType.ERROR);
					alert.setTitle("错误");
					alert.setHeaderText(null);
					alert.setContentText("文件不存在！");
					alert.showAndWait();
				}
			});
		} catch (IOException e) {
			e.printStackTrace();
			VoskController.vosk_bt_status(1, false);
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
			VoskController.vosk_bt_status(1, false);
		}
		VoskController.vosk_get.clear();
		for (Map.Entry<File, LinkedList<String>> entry : vosk_voice_files.entrySet()) {
			// 文件名 + 识别结果
			VoskController.vosk_get.appendText(entry.getKey().getName() + ":\n");
			for (String s : entry.getValue()) {
				if (!s.replace(" ", "").equals("")) {
					VoskController.vosk_get.appendText(s);
				}
			}
			VoskController.vosk_get.appendText("\n");
		}
		VoskController.vosk_bt_status(1, false);
	}

	/**
	 * 长语音文件识别 多线程：一个文件多个线程。
	 * 
	 * @param audio_file WAV PCM 16khz 16bit单声道 256 kbps
	 * @param model_dir  语音识别模型
	 * @return LinkedList<String> 识别结果
	 */
	public static List<String> voskLongVoiceFile(String audio_file, String model_dir) {
		// 设置识别状态为正在识别
		VoskController.vosk_bt_status(1, true);
		// 最终识别结果
		List<String> vosk_long_voice_file_ll = Collections.synchronizedList(new LinkedList<String>());
		LibVosk.setLogLevel(LogLevel.DEBUG);
		try (Model model = new Model(model_dir);
				InputStream ais = AudioSystem
						.getAudioInputStream(new BufferedInputStream(new FileInputStream(audio_file)));
				Recognizer recognizer = new Recognizer(model, 16000)) {

			VAD vad = null;
			// Windows下是用不了VAD4J的！
			if (!JofficeApp.isWindows) {
				vad = new VAD(VadWindowSize._30ms, VadMode.quality);
//				vad = new VAD();
			}

			final long TASK_SIZE = ais.available();
			System.out.println("可读取的字节数：" + TASK_SIZE);

			// byte数组最大数据量319815664
			final int MAX_BYTE = 319815664 - 40960;
			// 预留给vad分割的空间
			final int VAD_LEFT = 4096 * 15;
			// vad概率
			final double VAD_PROB = 0.75;
			// VAD 累计次数
			final int vad_cal = 5;
//			// 保留区
//			final int TASK_Pro = 40960;
			// 需要分割成几部分
			int parts;
			// 任务块大小
			int task_part_size;
			if (MAX_BYTE > (int) ais.available() / nThreads + VAD_LEFT) {
				// 如果vad预留空间加上音频的分割长度小于CPU数量，则按CPU数量分割
				parts = nThreads - 1;
			} else {
				// 如果vad预留空间加上音频的分割长度大于CPU数量，则按实际需求分割
				parts = ((int) ais.available() / (MAX_BYTE - VAD_LEFT));
			}
			task_part_size = (int) ais.available() / parts;
			System.out.println("当前CPU数量： " + nThreads + " 个核心，拟分割为" + parts + " 个任务块，每个任务块大小为： " + task_part_size);
			// 任务列表
			LinkedList<byte[]> task = new LinkedList<>();

			// 线程数 分割成几部分
			for (int i = 0; i < parts; i++) {
				task.add(new byte[0]);
			}

			int nbytes;
			byte[] b = new byte[4096];

			// vad状态改变的次数
			int vad_trigger_c = 0;
			// 已经读取的数据量
			int total_read_c = 0;
			// 单次读取的数据量
			int read_c = 0;
			// 当前任务索引
			int tp_index = 0;
			while ((nbytes = ais.read(b)) >= 0) {
				read_c = read_c + nbytes;
				total_read_c = total_read_c + nbytes;

				byte[] temp_b_a = JofficeToolKit.mergeBytes(task.get(tp_index), b);
				task.set(tp_index, temp_b_a);

				// 如果读取的数据量超出任务块大小保守值，强制分离。
				if (read_c >= task_part_size) {
					// 如果id小于拟分割数量的
					// 截断 任务块结束
					System.out.println("强制分割文件,添加了任务。");
					// 本次任务块添加完成,清零
					read_c = 0;
					vad_trigger_c = 0;
					tp_index += 1;
					if (tp_index == task.size()) {// 如果超出任务块数量
						read_c = 0;
						vad_trigger_c = 0;
						task.add(new byte[0]);
						System.out.println("新增任务块: " + task.size());
					} // 265856 1394432
				} else {// 这里是正常处理
						// 如果读取的数据量达到任务块除去vad分区大小 而且不是最后一个任务，开始进行vad
					if ((read_c >= task_part_size - VAD_LEFT) && (tp_index <= task.size())) {// 进入VAD分区
						double sp = 1.0;
						// 如果不是Windows
						if (!JofficeApp.isWindows) {
							sp = vad.speechProbability(b);
						}

						// 语音可能性小于70%? 截断
						if (sp < VAD_PROB) {
							vad_trigger_c += 1;
							// 识别到连续5次vad信号
							if (vad_trigger_c >= vad_cal) {
								// 截断 任务块结束
								// 本次任务块添加完成,清零
								System.out.println("疑似非语音部分，主动截断。");
								read_c = 0;
								vad_trigger_c = 0;
								tp_index += 1;
								if (tp_index == task.size()) {// 如果超出任务块数量
									task.add(new byte[0]);
									read_c = 0;
									vad_trigger_c = 0;
									System.out.println("VAD新增任务块: " + task.size());
								}
							}
						} else {
							// 正常处理
							vad_trigger_c = 0;
						}
					}
				}
			}

			System.out.println("\n----检查分割结果----\n任务大小：" + TASK_SIZE + "  理想任务块大小: " + task_part_size);
			int count = 0;
			int id = 0;
			for (byte[] t : task) {
				System.out.println(
						"任务ID：" + id + "  任务块大小：" + t.length + "  理想化占比：" + (double) t.length * 100 / task_part_size);
				// 删除空的任务
				if (t.length == 0) {
					System.out.println("删除空任务....");
					task.remove(id);
				}
				count += t.length;
				id += 1;
			}
			System.out.println("是否通过核验：" + (count >= TASK_SIZE));

			// 线程池的核心线程数：n个
			ExecutorService es = null;
			if (nThreads <= 1) {
				es = Executors.newFixedThreadPool((int) nThreads);
			} else {
				es = Executors.newFixedThreadPool((int) nThreads - 1);
			}
			// List集合用来存储Future
			LinkedList<Future<LinkedList<String>>> vosk_result_ll = new LinkedList<>();
			// 添加任务
			for (int i = 0; i < task.size(); i++) {
				// 提交任务之后，返回结果是Future，此时线程还没有结束，等待任务的返回值呢
				Future<LinkedList<String>> s = es.submit(new VoskTaskThread(i, task.get(i), model_dir));
				vosk_result_ll.add(s);
				vosk_long_voice_file_ll.add(new String());
			}

			// 获取结果
			for (Future<LinkedList<String>> f : vosk_result_ll) {
				try {
//					System.out.println(vosk_result_ll.indexOf(f)+"任务: "+f.get());
					int vosk_task_index = vosk_result_ll.indexOf(f);
					String tp_result = "";
					// 去除第一项
					f.get().removeFirst();
					for (String re : f.get()) {
						tp_result = tp_result + "  " + re;
					}
					vosk_long_voice_file_ll.set(vosk_task_index, tp_result);
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				}
			}
			es.shutdown();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(1, false);
			// 弹出错误指示框
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					Alert alert = new Alert(AlertType.ERROR);
					alert.setTitle("错误");
					alert.setHeaderText(null);
					alert.setContentText("文件不存在！");
					alert.showAndWait();
				}
			});
		} catch (IOException e) {
			e.printStackTrace();
			VoskController.vosk_bt_status(1, false);
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
			VoskController.vosk_bt_status(1, false);
		}
		VoskController.vosk_get.appendText("\n");
		for (String string : vosk_long_voice_file_ll) {
//			System.out.println("ID:" + vosk_long_voice_file_ll.indexOf(string) + "的结果：" + string);
			if (!string.replace(" ", "").equals("")) {
				VoskController.vosk_get.appendText(string);
			}
		}
		VoskController.vosk_bt_status(1, false);
		return vosk_long_voice_file_ll;
	}

	/**
	 * 音频流识别
	 * 
	 * @param i_s       音频流
	 * @param model_dir 语音识别模型
	 */
	public static void voskAudioInputStream(AudioInputStream i_s, String model_dir) {
		System.out.println(model_dir);
		// 设置识别状态为停止识别
		VoskController.vosk_bt_status(0, true);
		LibVosk.setLogLevel(LogLevel.DEBUG);
		try (Model model = new Model(model_dir);
				final InputStream ais = i_s;
				Recognizer recognizer = new Recognizer(model, 16000)) {

			int nbytes;
			byte[] b = new byte[4096];
			while ((nbytes = ais.read(b)) >= 0) {
				if (!VoskController.is_vosking || PrimaryController.fv_index != 0) {
					// 设置识别状态为停止识别
					VoskController.vosk_bt_status(0, false);
					System.out.println("Log：Vosk_Mic识别被终止(isVosking & FV_Changing) " + VoskController.is_vosking
							+ "    " + PrimaryController.fv_index);
					return;
				}
				if (recognizer.acceptWaveForm(b, nbytes)) {
//					System.out.println("R:" + voskResult(recognizer.getResult(), 0));
					VoskController.vosk_get.appendText(voskResult(recognizer.getResult(), 0));
//					System.out.println("F:"+voskResult(recognizer.getFinalResult(), 0));
				} else {
//					System.out.println("P:"+voskResult(recognizer.getPartialResult(), 1));
					recognizer.getPartialResult();
				}
			}
//			System.out.println("F:" + voskResult(recognizer.getFinalResult(), 0));
			VoskController.vosk_get.appendText(voskResult(recognizer.getFinalResult(), 0));
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(0, false);
		} catch (IOException e) {
			e.printStackTrace();
			// 设置识别状态为停止识别
			VoskController.vosk_bt_status(0, false);
		}
	}

	/**
	 * 识别默认的麦克风
	 * 
	 * @deprecated 失效的方法，保留
	 * @param model_dir 模型列表
	 */
	public static void voskDefaultMicrophone(String model_dir) {
		TargetDataLine target_data_line = null;
		AudioFormat format = JofficeToolKit.reqFormat();
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object

		if (!AudioSystem.isLineSupported(info)) { // If no default line
			System.out.println("The TargetDataLine is unavailable");
		}
		// Obtain and open the line.
		try {
			target_data_line = (TargetDataLine) AudioSystem.getLine(info);
			target_data_line.open(format);
			target_data_line.start();
		} catch (LineUnavailableException ex) {
			System.out.println("The TargetDataLine is Unavailable.");
		}

		AudioInputStream ais = new AudioInputStream(target_data_line);
		voskAudioInputStream(ais, "model_zh");
	}

	/**
	 * 识别指定麦克风
	 * 
	 * @param mixer     指定的Mixer
	 * @param model_dir 模型路径
	 */
	public static void voskSpecifiedMicrophone(Mixer.Info mixer, String model_dir) {
		String error = "";
		TargetDataLine target_data_line = null;
		AudioFormat format = JofficeToolKit.reqFormat();
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object

		if (!AudioSystem.isLineSupported(info)) { // If no default line
			System.out.println("The TargetDataLine is unavailable");
		}
		// Get a TargetDataLine on the selected mixer.
		try {
//			target_data_line = (TargetDataLine) AudioSystem.getLine(info);
			// 指定的麦克风
			target_data_line = (TargetDataLine) AudioSystem.getMixer(mixer).getLine(info);
			// Prepare the line for use.
			target_data_line.open(format);
			target_data_line.start();
		} catch (LineUnavailableException ex) {
			error = "The TargetDataLine is Unavailable." + ex.toString();
			System.out.println("The TargetDataLine is Unavailable: " + ex.toString());
		} catch (Exception e) {
			error = error + e.toString();
			System.out.println("ERROR：TargetDataLine出错:" + e.toString());
		}
		try {
			AudioInputStream ais = new AudioInputStream(target_data_line);
			voskAudioInputStream(ais, model_dir);
		} catch (Exception e) {
			error = error + e.toString();
			System.out.println("ERROR：AudioInputStream出错: " + e.toString());
			final String temp_error = error;
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					Alert alert = new Alert(AlertType.ERROR);
					alert.setTitle("错误");
					alert.setHeaderText(null);
					alert.setContentText(temp_error);
					alert.showAndWait();
				}
			});
		}
	}

	/**
	 * 识别指定扬声器
	 * 
	 * @param mixer     指定的Mixer
	 * @param model_dir 模型路径
	 */
	public static void voskSpecifiedSpeaker(Mixer.Info mixer, String model_dir) {
		String error = "";
		TargetDataLine target_data_line = null;
		AudioFormat format = JofficeToolKit.reqFormat();
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object

		if (!AudioSystem.isLineSupported(info)) { // If no default line
			System.out.println("The TargetDataLine is unavailable");
		}
		// Get a TargetDataLine on the selected mixer.
		try {
//			target_data_line = (TargetDataLine) AudioSystem.getLine(info);
			target_data_line = (TargetDataLine) AudioSystem.getMixer(mixer).getLine(info);
			// Prepare the line for use.
			target_data_line.open(format);
			target_data_line.start();
		} catch (LineUnavailableException ex) {
			error = "The TargetDataLine is Unavailable." + ex.toString();
			System.out.println("The TargetDataLine is Unavailable: " + ex.toString());
		} catch (Exception e) {
			error = error + e.toString();
			System.out.println("ERROR：TargetDataLine出错:" + e.toString());
		}
		try {
			AudioInputStream ais = new AudioInputStream(target_data_line);
			voskAudioInputStream(ais, model_dir);
		} catch (Exception e) {
			error = error + e.toString();
			System.out.println("ERROR：AudioInputStream出错: " + e.toString());
			final String temp_error = error;
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					Alert alert = new Alert(AlertType.ERROR);
					alert.setTitle("错误");
					alert.setHeaderText(null);
					alert.setContentText(temp_error);
					alert.showAndWait();
				}
			});
		}
	}

	/**
	 * 根据value值获取到对应的所有的文件 key值 vosk专用
	 * https://blog.csdn.net/weixin_34128237/article/details/86318176
	 * 
	 * @param map   文件列表
	 * @param value 值
	 * @return 文件列表 ArrayList
	 */
	public static List<File> getKeyFileList(LinkedHashMap<File, Integer> map, Integer value) {
		List<File> keyList = new ArrayList<>();
		for (File getKey : map.keySet()) {
			if (map.get(getKey).equals(value)) {
				keyList.add(getKey);
			}
		}
		return keyList;
	}

}

/**
 * @author ryan
 * @description: 单个Vosk语音识别的线程类
 * @param: int id 任务ID, byte[] task_byte 语音流数组, String model_dir识别模型
 * @return: LinkedList<String>的结果。
 */
class VoskTaskThread implements Callable<LinkedList<String>> {
	// 识别的任务数组
	private byte[] task_bytes = new byte[0];
	// 任务ID
	private int task_id = 0;
	// 任务状态
	// 0:初始化状态 1:任务执行中 2:任务完成
	private int task_status = 0;
	// 模型文件夹
	private String model_dir = null;
	// 识别结果
	private LinkedList<String> vosk_result = new LinkedList<String>();

	public VoskTaskThread(int id, byte[] task_byte, String model_dir) {
		this.setTask_id(id);
		this.setTask_bytes(task_byte);
		this.setModel_dir(model_dir);
		// 列表第一项是id
		vosk_result.add(String.valueOf(task_id));
	}

	@Override
	public LinkedList<String> call() throws Exception {
		System.out.println("ID：" + getTask_id() + " 的任务进行中。");
		task_status = 1;
		LibVosk.setLogLevel(LogLevel.DEBUG);
		try (Model model = new Model(model_dir);
				InputStream ais = new ByteArrayInputStream(task_bytes);
				Recognizer recognizer = new Recognizer(model, 16000)) {
			int nbytes;
			byte[] b = new byte[4096];
			while ((nbytes = ais.read(b)) >= 0) {
				// isvosking 停止
				if (!VoskController.is_vosking || PrimaryController.fv_index != 0) {
					// 设置识别状态为停止识别
					VoskController.vosk_bt_status(1, false);
					System.out.println("Log：Vosk_File_Thread" + getTask_id() + "识别被终止(isVosking & FV_Changing) "
							+ VoskController.is_vosking + "    " + PrimaryController.fv_index);
					break;
				}
				if (recognizer.acceptWaveForm(b, nbytes)) {
//					System.out.println(VoskUtil.voskResult(recognizer.getResult(), 0));
					vosk_result.add(VoskUtil.voskResult(recognizer.getResult(), 0));
				} else {
//					System.out.println("P:"+voskResult(recognizer.getPartialResult(), 1));
					recognizer.getPartialResult();
				}
			}
			vosk_result.add(VoskUtil.voskResult(recognizer.getFinalResult(), 0));
//			System.out.println(VoskUtil.voskResult(recognizer.getFinalResult(), 0));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("ID：" + getTask_id() + " 的任务已完成。");
			task_status = 2;
		}
		return vosk_result;
	}

	// getter & setter
	public String getModel_dir() {
		return model_dir;
	}

	public void setModel_dir(String model_dir) {
		this.model_dir = model_dir;
	}

	public int getTask_status() {
		return task_status;
	}

	public int getTask_id() {
		return task_id;
	}

	public void setTask_id(int task_id) {
		this.task_id = task_id;
	}

	public byte[] getTask_bytes() {
		return task_bytes;
	}

	public void setTask_bytes(byte[] task_bytes) {
		this.task_bytes = task_bytes;
	}

}
